import { CopyDeepTargetPropValPreHandlerType, CopyDeepOptionsType, CommonUtil } from '../../common'
import { ArrUtil } from '../../arr'
import { FunUtil } from '../../fun'
import { ObjUtil } from '../../obj'

/**
 * 拷贝原始对象属性的属性值到目标对象目标属性时，对属性值的处理函数
 *
 * @param {Record<string, any>} origin 原始对象
 * @param {string} originPropName 原始对象属性名
 * @param originPropVal 原始对象属性值
 * @param {string} targetPropName 目标对象目标属性名
 * @param {CopyDeepTargetPropValPreHandlerType} targetPropValPreHandler 原始对象中
 * 属性的属性值拷贝到目标对象的目标属性时的预处理函数
 * @returns {any} 处理后的属性值
 */
const copyPropValHandler = (
  origin: Record<string, any>,
  originPropName: string,
  originPropVal: any,
  targetPropName: string,
  targetPropValPreHandler: CopyDeepTargetPropValPreHandlerType
): any => {
  // 如果当前要拷贝到的目标对象目标属性的属性名指定了属性值的预处理函数
  if (Reflect.has(targetPropValPreHandler, targetPropName)) {
    // 获取当前要拷贝到的目标对象目标属性的属性值的预处理函数
    const handler = targetPropValPreHandler[targetPropName]
    // 调用属性值预处理函数，并返回处理后的属性值
    return handler(origin, originPropVal, originPropName, targetPropName)
  } else {
    return originPropVal
  }
}

/**
 * 深拷贝对象类型数据
 *
 * @param {Record<string, any>} obj 需要进行拷贝的对象
 * @param {CopyDeepOptionsType} options 深拷贝对象的配置选项：
 * - props 指定对象类型数据中需要拷贝的属性名称数组，如果不指定，即 props 数组的长度为 0，则
 * 拷贝对象类型数据所有属性，如果在 props 中指定了要拷贝的属性，即 props 的长度大于 0，则不在
 * props 中的属性将不会被拷贝，默认值 `[]`
 * - excludeProps 指定对象类型数据中需要排除拷贝的属性名称数组，如果不指定，即 props 数组的
 * 长度为 0，则不排除任何属性，如果相同的属性名同时出现在 props 和 excludeProps 中，excludeProps
 * 优先级更高，默认值 `[]`
 * - isCopyObjUndefinedProp 对于对象类型数据，原始对象中属性值为 undefined 的属性是否对其
 * 进行拷贝，默认值为 `true`
 * - isCopyObjNullProp 对于对象类型数据，原始对象中属性值为 null 的属性是否对其进行拷贝，默
 * 认值为 `true`
 * - isCopyObjEmptyArrProp 对于数组中对象类型数据，原始对象中属性值为空数组的属性是否进行拷
 * 贝，默认值为 true
 * - propsMap 对于对象类型数据，原始对象中的属性与拷贝目标对象属性之间的映射关系，即原始对象中
 * 指定属性的属性值要拷贝到目标对象中的哪些属性上，如果要从原始对象拷贝到目标对象上的属性没有指
 * 定映射关系，则使用原始对象中的属性名作为目标对象上的属性名，默认值 `{}`
 * - targetPropValPreHandler 对于对象类型数据，原始对象属性的属性值拷贝到目标对象目标属性时
 * 对目标对象目标属性属性值的预处理函数，该函数接收：
 *   - 目前正在拷贝的原始对象
 *   - 目前正在拷贝的原始对象属性递归处理完成的属性值
 *   - 目前正在拷贝的原始对象属性的属性名
 *   - 要拷贝到目标对象目标属性的属性名
 * 作为参数，该函数需要返回要赋值给目标对象目标属性的属性值，默认值 `{}`
 * - excludeArrItem 数组中元素的排除条件，<b style="color:red">在对数组类型的数据进行拷贝
 * 时，该条件的判断在所有判断之前执行</b>，该函数接收当前遍历到的数组元素作为参数，返回 true
 * 则表示不对该元素进行拷贝，返回 false 则不排除该元素的拷贝，默认值 `() => false`
 * - isCopyArrUndefinedItem 对于数组中值为 undefined 的元素，是否对其进行拷贝，默认值 `true`
 * - isCopyArrNullItem 对于数组中值为 null 的元素，是否对其进行拷贝，默认值 `true`
 * - isCopyArrEmptyArrItem 对于数组中值为空数组的元素，是否进行拷贝，默认值为 `true`
 * - sort 用于指定数组类型数据拷贝后的目标数组中元素排序的函数，该函数接收两个数组元素作为参数，
 * 该函数需要返回一个数字类型的数据，如果返回的为负数，a 排在 b 前面；如果返回 0，a 和 b 的相
 * 对位置不变；如果返回正数，a 排在 b 后面。当 sort 没有指定时，表示不对数组进行排序，默认值
 * `undefined`
 * @returns {Record<string, any>} 深拷贝后的结果数组
 */
export const copyDeep = (
  obj: Record<string, any>,
  options: CopyDeepOptionsType = {}
): Record<string, any> => {
  const {
    props,
    excludeProps,
    isCopyObjUndefinedProp,
    isCopyObjNullProp,
    isCopyObjEmptyArrProp,
    propsMap,
    targetPropValPreHandler,
    excludeArrItem,
    isCopyArrUndefinedItem,
    isCopyArrNullItem,
    isCopyArrEmptyArrItem,
    sort
  } = {
    props: [],
    excludeProps: [],
    isCopyObjUndefinedProp: true,
    isCopyObjNullProp: true,
    isCopyObjEmptyArrProp: true,
    propsMap: {},
    targetPropValPreHandler: {},
    excludeArrItem: () => false,
    isCopyArrUndefinedItem: true,
    isCopyArrNullItem: true,
    isCopyArrEmptyArrItem: true,
    sort: undefined,
    ...options
  }
  const target: Record<string, any> = {}
  // 遍历原始对象中所有的属性
  for (const key in obj) {
    // 如果当前遍历的属性名在 excludeProps 中，则跳过当前属性的拷贝
    if (!ArrUtil.isEmpty(excludeProps) && ArrUtil.isIncludeIn(excludeProps, key)) continue
    // 如果指定了需要拷贝的属性，且当前遍历的属性不在属性名数组中，则跳过当前属性的拷贝
    if (!ArrUtil.isEmpty(props) && !ArrUtil.isIncludeIn(props, key)) continue
    // 当前要拷贝属性的属性值
    let val: any = null
    // 如果当前遍历的属性值为 undefined
    if (CommonUtil.isUndefined(obj[key])) {
      // 如果配置了不对属性值为 undefined 的属性进行拷贝，则跳过当前属性的拷贝
      if (!isCopyObjUndefinedProp) continue
      val = obj[key]
    }
    // 如果当前遍历的属性的类型为 null
    else if (CommonUtil.isNull(obj[key])) {
      // 如果配置了不对属性值为 null 的属性进行拷贝，则跳过当前属性的拷贝
      if (!isCopyObjNullProp) continue
      val = obj[key]
    }
    // 如果当前遍历的属性的类型为 function, 则直接赋值
    else if (FunUtil.isFun(obj[key])) val = obj[key]
    // 如果当前遍历的属性的类型为数组, 则使用 arrCopyDeep 函数进行深拷贝
    else if (ArrUtil.isArr(obj[key])) {
      val = ArrUtil.copyDeep(obj[key], {
        props,
        excludeProps,
        isCopyObjUndefinedProp,
        isCopyObjNullProp,
        isCopyObjEmptyArrProp,
        propsMap,
        targetPropValPreHandler,
        excludeArrItem,
        isCopyArrUndefinedItem,
        isCopyArrNullItem,
        isCopyArrEmptyArrItem,
        sort
      })
      // 如果当前遍历的属性的属性值为空数组，且配置了不对属性值为空数组的属性进行拷贝
      if (ArrUtil.isEmpty(val) && !isCopyObjEmptyArrProp) continue
    }
    // 如果如果当前遍历的属性的类型为对象，则递归进行深拷贝
    else if (ObjUtil.isObj(obj[key])) {
      val = copyDeep(obj[key], {
        props,
        excludeProps,
        isCopyObjUndefinedProp,
        isCopyObjNullProp,
        isCopyObjEmptyArrProp,
        propsMap,
        targetPropValPreHandler,
        excludeArrItem,
        isCopyArrUndefinedItem,
        isCopyArrNullItem,
        isCopyArrEmptyArrItem,
        sort
      })
    }
    // 其他情况直接赋值
    else {
      val = obj[key]
    }
    // 如果指定了当前遍历属性与目标对象中属性的映射关系，则使用映射关系进行赋值
    if (Reflect.has(propsMap, key)) {
      // 获取当前遍历属性需要映射到的目标对象属性名
      const mapTo = propsMap[key]
      // 如果需要映射到目标对象中的多个属性
      if (ArrUtil.isArr(mapTo)) {
        mapTo.forEach(propName => {
          target[propName] = copyPropValHandler(obj, key, val, propName, targetPropValPreHandler)
        })
      } else {
        target[mapTo] = copyPropValHandler(obj, key, val, mapTo, targetPropValPreHandler)
      }
    } else {
      target[key] = copyPropValHandler(obj, key, val, key, targetPropValPreHandler)
    }
  }
  return target
}

export type CopyDeepType = typeof copyDeep
